/*
 * CS151 Group 1
 * Mancala AI Heuristic Code
 * 
 * Mohammad Noorzay, Daryl Hennegan, Justin Turpin, Robert Rodriguez, Peter Huynh
 */

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Random;

public class group1xs_KalahHeuristic {
	double h1_1; 	// Weights for part 1
	double h2_1;
	double h3_1;
	double h4_1;
	double h5_1;
	double h6_1;
	double h7_1;
	double h8_1;
	double h9_1;
	
	double h1_2; 	// Weights for part 2
	double h2_2;
	double h3_2;
	double h4_2;
	double h5_2;
	double h6_2;
	double h7_2;
	double h8_2;
	double h9_2;
	
	double h1_3; 	// Weights for part 3
	double h2_3;
	double h3_3;
	double h4_3;
	double h5_3;
	double h6_3;
	double h7_3;
	double h8_3;
	double h9_3;
	
	
	String name;
	public int child;
	
	double wins;
	int losses;
	int gamesPlayed;
	
	static Random random = new Random();
	
	@SuppressWarnings("rawtypes")
	public static class KalahComparator implements Comparator {
		@Override
		public int compare(Object t, Object t1) {
			group1xs_KalahHeuristic h1 = (group1xs_KalahHeuristic)t;
			group1xs_KalahHeuristic h2 = (group1xs_KalahHeuristic)t1;
			return (int)(4.0*(h1.wins - h2.wins));
		}
	
}
	/*
	 * Return a random float between a start and end range
	 */
	public static float v( double start, double end ) {
		float range = (float)end - (float)start;
		
		return random.nextFloat()*range + (float)start;
	}
	
	public group1xs_KalahHeuristic(){
		 h1_1 = 118.718255999029470;
		 h2_1 = 32.629023672868270;
		 h3_1 = -34.282860790566090;
		 h4_1 = -39.105233678333620;
		 h5_1 = -36.135350458323266;
		 h6_1 = 3.476018549235268;
		 h7_1 = 15.954433282795154;
		 h8_1 = 85.137813839145400; 
		 h9_1 = -58.315117618118480;
		 
		 h1_2 = 118.718255999029470;
		 h2_2 = 32.629023672868270;
		 h3_2 = -34.282860790566090;
		 h4_2 = -39.105233678333620;
		 h5_2 = -36.135350458323266;
		 h6_2 = 3.476018549235268;
		 h7_2 = 15.954433282795154;
		 h8_2 = 85.137813839145400; 
		 h9_2 = -58.315117618118480;
		 
		 h1_3 = 118.718255999029470;
		 h2_3 = 32.629023672868270;
		 h3_3 = -34.282860790566090;
		 h4_3 = -39.105233678333620;
		 h5_3 = -36.135350458323266;
		 h6_3 = 3.476018549235268;
		 h7_3 = 15.954433282795154;
		 h8_3 = 85.137813839145400; 
		 h9_3 = -58.315117618118480;
		 name = "0";
		 wins = 0.0;
		 losses = 0;
		 gamesPlayed = 0;
		 child = 0;
	}
	
	public group1xs_KalahHeuristic( double h1_1, double h2_1, double h3_1, double h4_1, double h5_1, double h6_1 , double h7_1,  double h8_1, double h9_1 ,
								  double h1_2, double h2_2, double h3_2, double h4_2, double h5_2, double h6_2 , double h7_2,  double h8_2, double h9_2 ,
								  double h1_3, double h2_3, double h3_3, double h4_3, double h5_3, double h6_3 , double h7_3,  double h8_3, double h9_3 ,
								String name) {
		this.h1_1 = h1_1;
		this.h2_1 = h2_1;
		this.h3_1 = h3_1;
		this.h4_1 = h4_1;
		this.h5_1 = h5_1;
		this.h6_1 = h6_1;
		this.h7_1 = h7_1;
		this.h8_1 = h8_1;
		this.h9_1 = h9_1;
		
		this.h1_2 = h1_2;
		this.h2_2 = h2_2;
		this.h3_2 = h3_2;
		this.h4_2 = h4_2;
		this.h5_2 = h5_2;
		this.h6_2 = h6_2;
		this.h7_2 = h7_2;
		this.h8_2 = h8_2;
		this.h9_2 = h9_2;
		
		this.h1_3 = h1_3;
		this.h2_3 = h2_3;
		this.h3_3 = h3_3;
		this.h4_3 = h4_3;
		this.h5_3 = h5_3;
		this.h6_3 = h6_3;
		this.h7_3 = h7_3;
		this.h8_3 = h8_3;
		this.h9_3 = h9_3;
		this.name = name;
		wins = 0.0;
		losses = 0;
		gamesPlayed = 0;
		child = 0;
	}
	
	public group1xs_KalahHeuristic(String name, double h1_1, double h2_1, double h3_1, double h4_1, double h5_1, double h6_1 , double h7_1,  double h8_1, double h9_1 ,
								  double h1_2, double h2_2, double h3_2, double h4_2, double h5_2, double h6_2 , double h7_2,  double h8_2, double h9_2 ,
								  double h1_3, double h2_3, double h3_3, double h4_3, double h5_3, double h6_3 , double h7_3,  double h8_3, double h9_3 , int children) {
		this.h1_1 = h1_1;
		this.h2_1 = h2_1;
		this.h3_1 = h3_1;
		this.h4_1 = h4_1;
		this.h5_1 = h5_1;
		this.h6_1 = h6_1;
		this.h7_1 = h7_1;
		this.h8_1 = h8_1;
		this.h9_1 = h9_1;
		
		this.h1_2 = h1_2;
		this.h2_2 = h2_2;
		this.h3_2 = h3_2;
		this.h4_2 = h4_2;
		this.h5_2 = h5_2;
		this.h6_2 = h6_2;
		this.h7_2 = h7_2;
		this.h8_2 = h8_2;
		this.h9_2 = h9_2;
		
		this.h1_3 = h1_3;
		this.h2_3 = h2_3;
		this.h3_3 = h3_3;
		this.h4_3 = h4_3;
		this.h5_3 = h5_3;
		this.h6_3 = h6_3;
		this.h7_3 = h7_3;
		this.h8_3 = h8_3;
		this.h9_3 = h9_3;
		this.name = name;
		wins = 0.0;
		losses = 0;
		gamesPlayed = 0;
		child = children;
	}
	
	
	/*
	 * Creates a copy of this heuristic
	 */
	public group1xs_KalahHeuristic copy() {
		return new group1xs_KalahHeuristic(h1_1, h2_1, h3_1, h4_1, h5_1, h6_1, h7_1, h8_1, h9_1,
										 h1_2, h2_2, h3_2, h4_2, h5_2, h6_2, h7_2, h8_2, h9_2,
										 h1_3, h2_3, h3_3, h4_3, h5_3, h6_3, h7_3, h8_3, h9_3, name );
	}
	
	
	public void resetStats(){
		this.wins = 0;
		this.losses = 0;
		this.gamesPlayed = 0;
				
	}
	
	
	@Override
	public String toString() {
		float winRatio =  (float)(wins/(float)gamesPlayed);
		return String.format( "Heuristic %-"+LearningEngine.length+"s: " +
				"%7.2f, %7.2f, %7.2f, %7.2f, %7.2f, %7.2f, %7.2f, %7.2f, %7.2f with %3.1f wins, %3d losses, %1.3f winRatio", name, h1_1, h2_1, h3_1, h4_1, h5_1, h6_1, h7_1, h8_1, h9_1, wins, losses, winRatio );
	}
	
	public int evaluate(KalahPieGameState gamestate, int player_number, HashMap<String, StateSimple> stateTable)
	{
		int val = 0;
		
		if( gamestate.checkEndGame() ) {
			
			gamestate.computeFinalScore();
			int player_score = gamestate.score[ player_number ];
			int enemy_score  = gamestate.score[ 1 - player_number ];
			
			if( player_score > enemy_score )
			{
				return Integer.MAX_VALUE/2;
			}
			else
			{
				return Integer.MIN_VALUE/2;
			}
		}
		
		int sum_score = gamestate.score[0] + gamestate.score[1];
		int total_stones = gamestate.NUMCOL * gamestate.NUMROW * gamestate.startingStones;
		int one_third_stones = total_stones / 3;
		int stones_left = total_stones - sum_score;
		
		if( stones_left >= 2*one_third_stones ) {
			// Use part 3 weights
			val += (int) (h1_3 * H1(gamestate, player_number));
			val += (int) (h2_3 * H2(gamestate, player_number));
			val += (int) (h3_3 * H3(gamestate, player_number));
			val += (int) (h4_3 * H4(gamestate, player_number));
			val += (int) (h5_3 * H5(gamestate, player_number));
			val += (int) (h6_3 * H6(gamestate, player_number));
			val += (int) (h7_3 * H7(gamestate, player_number));
			val += (int) (h8_3 * H8(gamestate, player_number));
			val += (int) (h9_3 * H9(gamestate, player_number));
		} else if( stones_left >= one_third_stones ) {
			// Use Part 2 weights
			val += (int) (h1_2 * H1(gamestate, player_number));
			val += (int) (h2_2 * H2(gamestate, player_number));
			val += (int) (h3_2 * H3(gamestate, player_number));
			val += (int) (h4_2 * H4(gamestate, player_number));
			val += (int) (h5_2 * H5(gamestate, player_number));
			val += (int) (h6_2 * H6(gamestate, player_number));
			val += (int) (h7_2 * H7(gamestate, player_number));
			val += (int) (h8_2 * H8(gamestate, player_number));
			val += (int) (h9_2 * H9(gamestate, player_number));
		} else {
			// Use Part 1 weights
			val += (int) (h1_1 * H1(gamestate, player_number));
			val += (int) (h2_1 * H2(gamestate, player_number));
			val += (int) (h3_1 * H3(gamestate, player_number));
			val += (int) (h4_1 * H4(gamestate, player_number));
			val += (int) (h5_1 * H5(gamestate, player_number));
			val += (int) (h6_1 * H6(gamestate, player_number));
			val += (int) (h7_1 * H7(gamestate, player_number));
			val += (int) (h8_1 * H8(gamestate, player_number));
			val += (int) (h9_1 * H9(gamestate, player_number));
		}
//		if (H10(gamestate, stateTable, player_number) != 0) {
//			System.out.println("Before " + val);
//			//System.out.println("Add" + H10(gamestate, stateTable, player_number) * 40000.0);
//		}
		val += (int) (H10(gamestate, stateTable, player_number) * 100.0);
//		if (H10(gamestate, stateTable, player_number) != 0) {
//			System.out.println("After " + val);
//		}
//		try {
//			FileWriter writer = new FileWriter("Values.txt", true);
//			writer.write(val + "\r\n");
//			writer.close();
//		} catch (IOException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
		return val;
	}
	/*
	 * Returns the number of game pieces that a player has on their side plus their current score
	 */

	private int H7( KalahPieGameState gamestate, int player_number ) {

		int sum = 0;
		for( int i = 0; i < gamestate.NUMCOL; i++ ) {
			sum += gamestate.stonesAt( player_number, i );
		}
		
		return sum + gamestate.score[ player_number ];
	}
	

	//------------------------------------------------------------------------------------------
	// Function: How far ahead of my opponent I am
	// Purpose: Stones in my home - stones in opponent's home.  Returns higher number the better
	// the situation is...if we (P1) are winning by more it is a better situation.
	//------------------------------------------------------------------------------------------
	private int H1(KalahPieGameState game, int player_number)
	{
		// Return (stones in my home - stones in opponent's home)
		
		int enemy_player_number = 1 - player_number;
		int player_score = game.score[ player_number ];
		int enemy_score  = game.score[ enemy_player_number ];
		return player_score - enemy_score;
	}

	//------------------------------------------------------------------------------------------
	// Function: How close I am to winning
	// Purpose: 1 + half total stones in play.  Returns higher number the better the situation
	//			is...if we are close to winning, we will have a higher value returned (better).
	//------------------------------------------------------------------------------------------
	private int H2(KalahPieGameState game, int player_number)
	{
		// Return (total number of stones - how many we need to guarantee win)
		int TotalStones = 0;
		for(int i = 0; i<2 ;i++)
			for(int j=0; j<6; j++)
			TotalStones =+game.stonesAt(i, j);
		
		int half = TotalStones/2;
		return (TotalStones - (half+1 - game.score[player_number]));
	}

	//------------------------------------------------------------------------------------------
	// Function: How close opponent is to winning
	// Purpose: 1 + half total stones in play.  Returns higher number the better the situation
	//	        is...if opponent is close to winning, a higher value returned.
	//------------------------------------------------------------------------------------------
	private int H3(KalahPieGameState game, int player_number)
	{
		// Return (total number of stones - how many opponent needs to guarantee win)
		int TotalStones = 0;
		for(int i = 0; i<2 ;i++)
			for(int j=0; j<6; j++)
			TotalStones =+game.stonesAt(i, j);
		
		int half = TotalStones/2;
		return (TotalStones - (half+1 - game.score[1-player_number]));
	}

	//------------------------------------------------------------------------------------------
	// Function: Number of stones close to my home
	// Purpose: Looks at 1/3 closest bins to player's home mancala and sums them.
	//------------------------------------------------------------------------------------------
	private int H4(KalahPieGameState game, int player_number)
	{
		// Number of bins to look at
		int num = game.cols()/3;

		// Summing up contents of closest 1/3 bins to home mancala
		int sum = 0;
		for(int i=0; i<num; i++)
			sum += game.grid[player_number][i];
		
		// Return sum of closest 1/3 bins to home mancala
		return sum;
	}

	//------------------------------------------------------------------------------------------
	// Function: Number of stones far away from my home
	// Purpose: Looks at 1/3 farthest bins away from player's home mancala and sums them.
	//------------------------------------------------------------------------------------------
	private int H5(KalahPieGameState game, int player_number)
	{
		// Number of bins to look at
		int num = game.cols()/3;

		// Summing up contents of closest 1/3 bins to home mancala
		int sum = 0;
		for(int i=num; i>0; i--)
			sum += game.grid[player_number][i];
				
		// Return sum of closest 1/3 bins to home mancala
		return sum;
	}

	//------------------------------------------------------------------------------------------
	// Function: Number of stones in middle of board (neither close nor far from home mancala)
	// Purpose: Looks at 1/3 middle bins of the board and sums them.
	//------------------------------------------------------------------------------------------
	private int H6(KalahPieGameState game, int player_number)
	{
		// Number of bins to look at
		int num = game.cols()/3;

		// Summing up contents of closest 1/3 bins to home mancala
		int sum = 0;
		for(int i=0; i<num; i++)
			sum += game.grid[player_number][num+i];

		// Return sum of middle 1/3 bins 
		return sum;
	}
	
	//------------------------------------------------------------------------------------------
	// Function: Number of stones in the players own corner spaces
	//------------------------------------------------------------------------------------------
	private int H8(KalahPieGameState game, int player_number) {
		int left_corner  = game.grid[ player_number ][ 0 ];
		int right_corner = game.grid[ player_number ][ game.NUMCOL - 1];
		return left_corner + right_corner;
	}
	
	//------------------------------------------------------------------------------------------
	// Function: Number of stones in the enemies corner spaces
	//------------------------------------------------------------------------------------------
	private int H9(KalahPieGameState game, int player_number) {
		int left_corner  = game.grid[ 1 - player_number ][ 0 ];
		int right_corner = game.grid[ 1 - player_number ][ game.NUMCOL - 1];
		return left_corner + right_corner;
	}
	
	//------------------------------------------------------------------------------------------
	// Function: Value of the game state
	//------------------------------------------------------------------------------------------
	private double H10(KalahPieGameState game, HashMap<String, StateSimple> stateTable, int player_number) {
		String state = StateSimple.codifyState(game, 1 - player_number);
//		BufferedWriter writer;
//		try {
//			writer = new BufferedWriter(new FileWriter("Test.txt"));
//			writer.write(state + "\r\n");
//			writer.flush();
//		} catch (IOException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
		if (stateTable.containsKey(state)) {
			return stateTable.get(state).value;
		} else {
			return 0.0;
		}
	}
}
